﻿using ServerListerner.Enums;
using ServerListerner.Handlers;
using ServerListerner.Helper;
using ServerListerner.Model;

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace ServerListerner.Connections
{
    internal class HTTPConnection : IHTTPConnection
    {
        public Action<IHTTPConnection, HTTPRequest> OnReceived { get; set; }
        private Socket _socket;
        private bool IsClosed = false;
        private int _bufferLen = 0;
        private byte[] _buffer = null;
        private int _sendBufferLen = 0;
        private byte[] _sendBuffer = null;
        private List<byte> _received = new List<byte>();
        private readonly Encoding? _encoding = null;
        public EndPoint? RemoteEndPoint { get; private set; }
        public List<ListenPath> _path = new List<ListenPath>();
        private string? Version = string.Empty;
        private Dictionary<string, string> _Header = null;
        private IRouterHandler? _handler;


        public HTTPConnection(Socket socket, Action<IHTTPConnection>? action, List<ListenPath> path, IRouterHandler handler = null, Encoding? encoding = null)
        {
            this._socket = socket;
            this._path.Clear();
            if (action != null)
                this._path = path;
            this._bufferLen = socket.ReceiveBufferSize;
            this._sendBufferLen = socket.SendBufferSize;
            this._encoding = encoding ?? Encoding.UTF8;
            this.RemoteEndPoint = socket.RemoteEndPoint;
            this._handler = handler;
            action?.Invoke(this);
            _ = Task.Run(SendData);
            Task.Run(StartReceived);
        }
        private void Send(byte[] bytes) => this.BeginSend(bytes);
        public void Send(string Message, Dictionary<string, string> Header = null)
        {
            this._Header = this._Header?.AddHeader(Header) ?? new Dictionary<string, string>();
            HTTPResponse response = new HTTPResponse()
            {
                Content = Message,
                Code = HttpStatusCode.OK,
                Headers = this._Header,
                Version = this.Version,
            };
            byte[]? _sendByte = response.HTTPEncoder();
            if (_sendByte == null || _sendByte.Length <= 0)
            {
                this.Close();
                return;
            }
            this.BeginSend(_sendByte);

        }
        public void Dispose() => this.Close();
        private void StartReceived()
        {
            try
            {
                while (!IsClosed)
                {
                    this._buffer = null;
                    this._buffer = new byte[this._bufferLen];
                    if (!this._socket.Connected) this.Close();
                    if (IsClosed) return;
                    var iar = this._socket.BeginReceive(this._buffer, 0, _bufferLen, SocketFlags.None, null, null);
                    int _readLen = this._socket.EndReceive(iar);
                    if (_readLen == 0) Close();
                    if (IsClosed) return;
                    if (this._received == null)
                        this._received = new List<byte>();
                    this._received.AddRange(this._buffer.Take(_readLen));
                    Task.Delay(1).Wait();
                    if (this._socket.Available <= 0)
                    {
                        channel.Send(this._received.ToArray());
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
            }
           
        }
        MpscChannel<byte[]> channel = new MpscChannel<byte[]>();
        //private void Receiveding(IAsyncResult iar)
        //{
        //    if (!this._socket.Connected) this.Close();
        //    if (IsClosed) return;
        //    int _readLen = this._socket.EndReceive(iar);
        //    if (_readLen == 0) Close();
        //    if (IsClosed) return;
        //    if (this._received == null)
        //        this._received = new List<byte>();
        //    this._received.AddRange(this._buffer.Take(_readLen));
        //    Task.Delay(1).Wait();
        //    if (this._socket.Available <= 0)
        //    {
        //        channel.Send(this._received.ToArray());
        //        return;
        //    }
        //    StartReceived();
        //}
        private void SendData()
        {
            while (!IsClosed)
            {
                if (channel.TryReceive(out byte[] data))
                {
                    EndReceived(data);
                    Task.Run(StartReceived);
                }

            }
        }
        private void EndReceived(byte[] buffer)
        {
            try
            {
                this._received.Clear();
                if (buffer == null || buffer.Length <= 0) return;
                HTTPRequest request = buffer.HTTPDecoder(buffer.Length);
                request.Connection = this;
                this.Version = request.Version;
                this._Header = request.Headers?.RemoveHeaderHost() ?? new Dictionary<string, string>();
                var method = this._handler?.Routes.Where(s => s.Path.ToLower() == request.Url.ToLower() && s.RequestMethod?.ToLower() == request.Method.ToLower()).FirstOrDefault();
                if (method != null)
                {
                    object? obj = this._handler?.HandleRequest(method, request);
                    string? resp = obj.ResultData();
                    this.Send(new HTTPResponse()
                    {
                        Version = this.Version,
                        Code = HttpStatusCode.OK,
                        Headers = this._Header,
                        Content = resp
                    }.HTTPEncoder());
                    return;
                }
                if (!this._path.Any(s => s.Method?.ToLower() == request.Method?.ToLower() && s.Path?.ToLower() == request.Url?.ToLower()))
                {
                    this.Send(new HTTPResponse()
                    {
                        Version = request.Version,
                        Code = HttpStatusCode.NotFound,
                        Headers = this._Header,
                        Content = "Not Fount"
                    }.HTTPEncoder());
                    return;
                }
                if (OnReceived != null)
                    OnReceived?.Invoke(this, request);
                else
                    this.Send(new HTTPResponse()
                    {
                        Version = this.Version,
                        Code = HttpStatusCode.InternalServerError,
                        Headers = this._Header,
                        Content = "Internal Server Error"
                    }.HTTPEncoder());
                return;
            }
            catch (Exception ex)
            {
                //OnError?.Invoke(this, ex);    
                this.Send(new HTTPResponse()
                {
                    Version = this.Version,
                    Code = HttpStatusCode.InternalServerError,
                    Headers = this._Header,
                    Content = "Internal Server Error"
                }.HTTPEncoder());
                return;
            }

        }

        private void BeginSend(byte[] bytes)
        {
            try
            {
                while (bytes.Length > 0)
                {
                    var send = bytes.Take(this._sendBufferLen).ToArray();
                    int len = send.Length > this._sendBufferLen ? this._sendBufferLen : send.Length;
                    bytes = bytes.Skip(len).ToArray();
                    if (!this._socket.Connected)
                        return;
                    var iar = this._socket.BeginSend(send, 0, len, SocketFlags.None, null,null);
                    this._socket.EndSend(iar);
                }
            }
            catch (Exception ex)
            {
                this.Send(new HTTPResponse()
                {
                    Version = this.Version,
                    Code = HttpStatusCode.InternalServerError,
                    Headers = this._Header,
                    Content = "Internal Server Error"
                }.HTTPEncoder());
            }
            this.Close();
            if (IsClosed)
                return;


        }
        //private void SendCallback(IAsyncResult iar)
        //{
        //    try
        //    {
        //        if (this._socket.Connected)
        //        {
        //            byte[] bytes = (byte[])iar.AsyncState;
        //            int size = this._socket.EndSend(iar);
        //            if (bytes.Length > 0)
        //                this._socket.BeginSend(send, 0, len, SocketFlags.None, SendCallback, bytes);
        //            BeginSend(bytes);
        //        }
        //    }
        //    catch (Exception ex)
        //    {
        //        this.Send(new HTTPResponse()
        //        {
        //            Version = this.Version,
        //            Code = HttpStatusCode.InternalServerError,
        //            Headers = this._Header,
        //            Content = "Internal Server Error"
        //        }.HTTPEncoder());
        //    }
        //    this.Close();
        //    if (IsClosed)
        //        return;
        //}


        private void Close()
        {
            this.IsClosed = true;
            //OnClosed?.Invoke(this);
            this.CloseSocket();
            this.SetNull();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            //GC.Collect();
        }
        private void CloseSocket()
        {
            this._socket?.Close();
            this._socket?.Dispose();
        }
        private void SetNull()
        {
            //this.OnClosed = null;
            //this.OnError = null;
            this.OnReceived = null;
            this._buffer = null;
            this._path = null;
            this.Version = string.Empty;
            this._Header = null;
        }
    }
}
